package com.choosefine.paycenter.common.aop;

import com.choosefine.paycenter.common.dto.FieldValidError;
import com.choosefine.paycenter.common.exception.ParamValidException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Created by Jay Chang on 2017/3/5.
 */
@Slf4j
@Aspect
@Component
public class RequestParamValidAspect{
    private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

    private final ExecutableValidator validator = factory.getValidator().forExecutables();

    private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    @Pointcut("execution(* com.choosefine.paycenter.web.controller.*.*(..))")
    public void beforeControllerInvoke(){
        // Do nothing because of this method is a pointcut
    }

    @Before("beforeControllerInvoke()")
    public void doBeforeControllerInvoke(JoinPoint point) throws NoSuchMethodException,ParamValidException {
        //  获得切入目标对象
        Object target = point.getThis();
        // 获得切入方法参数
        Object [] args = point.getArgs();
        // 获得切入的方法
        Method method = ((MethodSignature)point.getSignature()).getMethod();

        // 执行校验，获得校验结果
        Set<ConstraintViolation<Object>> validResult = validMethodParams(target, method, args);

        if (!validResult.isEmpty()) {
            String [] parameterNames = parameterNameDiscoverer.getParameterNames(method); // 获得方法的参数名称数组
            Iterator<ConstraintViolation<Object>> iterator = validResult.iterator();
            List<FieldValidError> fieldValidErrors = new ArrayList<>();
            while(iterator.hasNext()){
                ConstraintViolation<Object> constraintViolation  = iterator.next();
                PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();// 获得校验的参数路径信息
                int paramIndex = pathImpl.getLeafNode().getParameterIndex(); // 获得校验的参数位置
                String paramName = parameterNames[paramIndex];  // 获得校验的参数名称
                FieldValidError fieldValidError = new FieldValidError(paramName,constraintViolation.getMessage());//将需要的信息包装成简单的对象，方便后面处理
                fieldValidErrors.add(fieldValidError);
            }
            throw new ParamValidException(fieldValidErrors);//抛到上层由GlobalExceptionHanler统一处理
        }
    }

    private <T> Set<ConstraintViolation<T>> validMethodParams(T obj, Method method, Object [] params){
        return validator.validateParameters(obj, method, params);
    }
}